﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using LinqExpression = System.Linq.Expressions.Expression;
using System.Diagnostics.Contracts;

namespace SimpleData.Infrastructure.JavaScript
{
	public sealed class SymbolTable
	{
		private Dictionary<string, ExpressionSymbol> symbols = new Dictionary<string, ExpressionSymbol>( StringComparer.InvariantCultureIgnoreCase );

		public SymbolTable()
			: this( null )
		{ }

		public SymbolTable( SymbolTable parent )
		{
			this.Parent = parent;
		}

		public SymbolTable Parent
		{ get; private set; }


		public ExpressionSymbol Declare( string name, SymbolKind kind )
		{
			return this.Declare( name, kind, null );
		}

		public ExpressionSymbol Declare( string name, SymbolKind kind, LinqExpression variable )
		{
			Contract.Requires<ArgumentNullException>( !String.IsNullOrEmpty( name ) );

			if ( this.symbols.ContainsKey( name ) )
				throw new InvalidOperationException( "O símbolo já foi declarado." );

			ExpressionSymbol symbol = new ExpressionSymbol( name, kind, this );
			if ( variable != null )
				symbol.Expression = variable;
			this.symbols.Add( name, symbol );
			return symbol;
		}

		/// <summary>
		/// Obtém todas as variáveis que foram declaradas no contexto local
		/// </summary>
		/// <returns></returns>
		public IEnumerable<ParameterExpression> GetVariables()
		{
			var data = ( from symbol in this.symbols.Values
						 where symbol.Kind == SymbolKind.Variable
						 select ( ParameterExpression )symbol.Expression );
			return data;
		}

		/// <summary>
		/// Retorna as funções declaradas no contexto
		/// </summary>
		/// <returns></returns>
		public IEnumerable<LambdaExpression> GetFunctions()
		{
			var data = ( from symbol in this.symbols.Values
						 where symbol.Kind == SymbolKind.Function
						 select ( LambdaExpression )symbol.Expression );
			return data;
		}

		public bool TryGetSymbol( string name, out ExpressionSymbol symbol )
		{
			SymbolTable table = this;

			while ( table != null )
				if ( table.symbols.TryGetValue( name, out symbol ) )
					return true;
				else
					table = table.Parent;

			symbol = null;
			return false;
		}

		public bool TryGetExpressionAs<TExpression>( string name, out TExpression exp )
			where TExpression : LinqExpression
		{
			ExpressionSymbol symbol;

			if ( this.TryGetSymbol( name, out symbol ) )
				exp = symbol.Expression as TExpression;
			else
				exp = default( TExpression );

			return exp != null;
		}
	}

}
